/**************************************************************************************************
 * light library                                                                                  *
 * Copyright © 2012 David Kretzmer                                                                *
 *                                                                                                *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software  *
 * and associated documentation files (the "Software"), to deal in the Software without           *
 * restriction,including without limitation the rights to use, copy, modify, merge, publish,      *
 * distribute,sublicense, and/or sell copies of the Software, and to permit persons to whom the   *
 * Software is furnished to do so, subject to the following conditions:                           *
 *                                                                                                *
 * The above copyright notice and this permission notice shall be included in all copies or       *
 * substantial portions of the Software.                                                          *
 *                                                                                                *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING  *
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND     *
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.        *
 *                                                                                                *
 *************************************************************************************************/

#pragma once

#include "light/string/utf.hpp"
#include "light/diagnostics/errors.hpp"
#include "light/utility/safe_int_cast.hpp"

#include <cstdio> // sprintf
#include <cstdlib> // strto*
#include <cstring> // strlen
#include <algorithm>


namespace light
{
namespace string
{
	namespace internal
	{
		// Converts the formatting description in 'fmt' to a C-format using type 'type'
		char const* to_cfmt(char const *fmt, char const *type);

		// Writes the string specified by 'begin' and 'end' to 'writer'.
		template<typename DeviceWriter>
		DeviceWriter write(DeviceWriter writer, char const *begin, char const *end)
		{
			auto length = end - begin;
			writer.reserve(length);
			while(begin != end)
				*writer++ = *begin++;

			return writer; //::std::copy(begin, end, writer);
		}
	}


	//=============================================================================================
	// Convert the value 'val' to a string using the format 'fmt' and write the result to the
	// device 'out'.
	//
	// TODO: consider using intmax_t/uintmax_t as the only integer types to actually print/scan instead
	//       of long long/unsigned long long because in contrast to the latter ones they are guaranteed
	//       to be the largest integer type available on the platform.
	//       See https://www.securecoding.cert.org/confluence/display/seccode/INT15-C.+Use+intmax_t+or+uintmax_t+for+formatted+IO+on+programmer-defined+integer+types
	//=============================================================================================
	// c-string
	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, char const *val, char const* = "{}")
	{
		return internal::write(writer, val, val + strlen(val));
	}

	// std::string
	template<typename DeviceWriter>
	DeviceWriter write_to(DeviceWriter writer, std::string const &str, char const * = "{}")
	{
		return internal::write(writer, str.begin(), str.end());
	}

	// long long int
	template<typename DeviceWriter>
	DeviceWriter write_to(DeviceWriter writer, long long int val, char const *fmt = "{}")
	{
		size_t const length = sizeof("-9223372036854775808");
		static char buffer[length] = {};
		int written = ::std::snprintf(buffer, length, internal::to_cfmt(fmt, "lld"), val);

		return internal::write(writer, buffer, buffer + written);
	}

	template<typename DeviceWriter>
	DeviceWriter write_to(DeviceWriter writer, unsigned long long int val, char const *fmt = "{}")
	{
		size_t const length = sizeof("18446744073709551615");
		static char buffer[length] = {};
		int written = ::std::snprintf(buffer, length, internal::to_cfmt(fmt, "llu"), val);

		return internal::write(writer, buffer, buffer + written);
	}

	// char
	template<typename DeviceWriter>
	DeviceWriter write_to(DeviceWriter writer, char val, char const *fmt = "{}")
	{
		static char buffer[2] = {};
		int length = ::std::snprintf(buffer, 2, internal::to_cfmt(fmt, "c"), val);

		return internal::write(writer, buffer, buffer + length);
	}

	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, unsigned char val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<unsigned long long int>(val), fmt);
	}

	// short
	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, short val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<long long int>(val), fmt);
	}

	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, unsigned short val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<unsigned long long int>(val), fmt);
	}

	// int
	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, int val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<long long int>(val), fmt);
	}

	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, unsigned int val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<unsigned long long int>(val), fmt);
	}

	// long int
	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, long int val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<long long int>(val), fmt);
	}

	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, unsigned long int val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<unsigned long long int>(val), fmt);
	}

	// double
	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, double val, char const *fmt = "{}")
	{
		static char buffer[32] = {};
		int length = ::std::snprintf(buffer, 32, internal::to_cfmt(fmt, "f"), val);

		return internal::write(writer, buffer, buffer + length);
	}

	// float
	template<typename DeviceWriter>
	inline DeviceWriter write_to(DeviceWriter writer, float val, char const *fmt = "{}")
	{
		return write_to(writer, static_cast<double>(val), fmt);
	}


	//=============================================================================================
	//
	//=============================================================================================
	template<typename DeviceWriter>
	DeviceWriter printf_to(DeviceWriter writer, char const *fmt)
	{
		return internal::write(writer, fmt, fmt + strlen(fmt));
	}

	template<typename DeviceWriter, typename Arg1, typename ...Args>
	DeviceWriter printf_to(DeviceWriter writer, char const *fmt, Arg1 const &arg, Args const &...args)
	{
		char const *fmt_end = fmt + strlen(fmt);
		char const *arg_pos = std::find(fmt, fmt_end, '{');
		if(arg_pos == fmt_end)
		{
			// There are no more placeholder left, so we just append the args
			// to the end
			writer = internal::write(writer, fmt, fmt_end);
			writer = write_to(writer, arg);

			return printf_to(writer, fmt_end, args...);
		}
		else
		{
			writer = internal::write(writer, fmt, arg_pos);
			writer = write_to(writer, arg, arg_pos);

			char const *arg_end = std::find(arg_pos, fmt_end, '}');
			if(arg_end == fmt_end)
				throw runtime_error("closing brace expected");

			return printf_to(writer, arg_end + 1, args...);
		}
	}


	//=============================================================================================
	//
	//=============================================================================================
	template<typename DeviceWriter, typename Arg1, typename ...Args>
	DeviceWriter print_to(DeviceWriter writer, Arg1 const &arg, Args const &...args)
	{
		return printf_to(writer, "", arg, args...);
	}


	//=============================================================================================
	//
	//=============================================================================================
	template<typename Writer, typename ForwardIterator>
	Writer print_range(Writer writer, ForwardIterator begin, ForwardIterator end,
					   char const * separator, char const *fmt = "{}")
	{
		if(begin != end)
		{
			while(true)
			{
				writer = write_to(writer, *begin++, fmt);
				if(begin != end)
					writer = write_to(writer, separator);
				else
					break;
			}
		}

		return writer;
	}


	//=============================================================================================
	//
	//=============================================================================================	
	namespace internal
	{
		template<typename Iterator>
		Iterator find_next_char(Iterator begin, Iterator end)
		{
			return ::std::find_if(begin, end, [](char c) { return !::std::isspace(c); });
		}

		// Copys the string representation of an integer to 'result'
		template<typename IteratorIn, typename IteratorOut>
		IteratorOut check_copy_number(IteratorIn &begin, IteratorIn end, IteratorOut result)
		{
			if(begin != end)
			{
				if(!::std::isdigit(*begin) && *begin != '-' && *begin != '+')
					throw runtime_error("number expected");

				*result++ = *begin++;
				while(begin != end)
				{
					if(!::std::isdigit(*begin))
						break;

					*result++ = *begin++;
				}
			}

			return result;
		}

		// Copys the string representation of a real number to 'result'
		template<typename IteratorIn, typename IteratorOut>
		IteratorOut check_copy_real(IteratorIn &begin, IteratorIn end, IteratorOut result)
		{
			auto result_cur = check_copy_number(begin, end, result);
			if(begin != end)
			{
				if(*begin == '.') // TODO: Use locale
				{
					*result_cur++ = *begin++;
					result_cur = check_copy_number(begin, end, result_cur);
				}
				else if(*begin == 'e' || *begin == 'E')
				{
					*result_cur++ = *begin++;
					result_cur = check_copy_number(begin, end, result_cur);

					return result_cur;
				}
			}

			return result_cur;
		}

		// Compares the input (reader) with the format string. If they differ from each other, an exception is thrown
		template<typename DeviceReader>
		DeviceReader cmp_input_and_fmt(DeviceReader reader, char const *fmt_begin, char const *fmt_end)
		{
			while(fmt_begin != fmt_end)
			{
				if(::std::isspace(*fmt_begin))
				{
					++fmt_begin;
					continue;
				}
				if(::std::isspace(*reader))
				{
					++reader;
					continue;
				}

				if(*reader++ != *fmt_begin++)
					throw runtime_error("input doesn't match format string");
			}

			return reader;
		}

	} // namespace: internal


	// std::string
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, ::std::string &val, char const * = "{}")
	{
		auto send = end(reader);

		auto str_begin = internal::find_next_char(reader, send);
		if(str_begin == send)
			throw runtime_error("unexpected end");

		// Read until first non-alpha-numeric character. TODO: add support for arbitrary termination character
		auto str_end = ::std::find_if(str_begin, send, [](char c){ return !::std::isalnum(c); });
		val.assign(str_begin, str_end);

		return str_end;
	}

	// long long int
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, long long int &val, char const *fmt = "{}")
	{
		auto str_end = end(reader);
		auto number_begin = internal::find_next_char(reader, str_end);

		static char buffer[sizeof("-9223372036854775808")] = {};

		// Copy the number to the buffer and terminate the end with a zero
		auto buffer_end = internal::check_copy_number(number_begin, str_end, buffer); // 'number_begin' is being increased by 'check_copy_number'
		*buffer_end = '\0';

		// Convert the string to a number
		auto res = ::std::sscanf(buffer, internal::to_cfmt(fmt, "lld"), &val);
		if(res == 0 || res == EOF)
			throw runtime_error("error while parsing string");

		// Set the reader to the next character to read
		return number_begin;
	}

	// unsigned long long int
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, unsigned long long int &val, char const *fmt = "{}")
	{
		auto str_end = end(reader);
		auto number_begin = internal::find_next_char(reader, str_end);

		static char buffer[sizeof("18446744073709551615")] = {};

		// Copy the number to the buffer and terminate the end with a zero
		auto buffer_end = internal::check_copy_number(number_begin, str_end, buffer); // 'number_begin' is being increased by 'check_copy_number'
		*buffer_end = '\0';

		// Convert the string to a number
		auto res = ::std::sscanf(buffer, internal::to_cfmt(fmt, "llu"), &val);
		if(res == 0 || res == EOF)
			throw runtime_error("error while parsing string");

		// Set the reader to the next character to read
		return number_begin;
	}

	// short
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, short &val, char const *fmt = "{}")
	{
		long long int ll;
		auto r = read_from(reader, ll, fmt);
		val = safe_int_cast<short>(ll);

		return r;
	}

	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, unsigned short &val, char const *fmt = "{}")
	{
		unsigned long long int ull;
		auto r = read_from(reader, ull, fmt);
		val = safe_int_cast<unsigned short>(ull);

		return r;
	}

	// int
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, int &val, char const *fmt = "{}")
	{
		long long int ll;
		auto r = read_from(reader, ll, fmt);
		val = safe_int_cast<int>(ll);

		return r;
	}

	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, unsigned int &val, char const *fmt = "{}")
	{
		unsigned long long int ull;
		auto r = read_from(reader, ull, fmt);
		val = safe_int_cast<unsigned int>(ull);

		return r;
	}

	// long int
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, long int &val, char const *fmt = "{}")
	{
		long long int ll;
		auto r = read_from(reader, ll, fmt);
		val = safe_int_cast<long int>(ll);

		return r;
	}

	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, unsigned long int &val, char const *fmt = "{}")
	{
		unsigned long long int ull;
		auto r = read_from(reader, ull, fmt);
		val = safe_int_cast<unsigned long int>(ull);

		return r;
	}

	// char
	template<typename DeviceReader>
	inline DeviceReader read_fom(DeviceReader out, char &val, char const *fmt = "{}")
	{
		(void)out;
		(void)val;
		(void)fmt;
		static_assert(!sizeof(DeviceReader), "TODO");
	}

	// double
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, double &val, char const *fmt = "{}")
	{
		auto str_end = end(reader);
		auto number_begin = internal::find_next_char(reader, str_end);

		static char buffer[512] = {};

		// Copy the number to the buffer and terminate the end with a zero
		auto buffer_end = internal::check_copy_real(number_begin, str_end, buffer); // 'number_begin' is being increased by 'check_copy_number'
		*buffer_end = '\0';

		// Convert the string to a number
		auto res = ::std::sscanf(buffer, internal::to_cfmt(fmt, "lf"), &val);
		if(res == 0 || res == EOF)
			throw runtime_error("error while parsing string");

		// Set the reader to the next character to read
		return number_begin;
	}

	// float
	template<typename DeviceReader>
	inline DeviceReader read_from(DeviceReader reader, float &val, char const *fmt = "{}")
	{
		double d;
		auto r = read_from(reader, d, fmt);
		val = static_cast<float>(d);

		return r;
	}


	//=============================================================================================
	//
	//=============================================================================================
	template<typename DeviceReader>
	DeviceReader scanf_from(DeviceReader reader, char const *fmt)
	{
		auto *fmt_end = fmt + strlen(fmt);
		return internal::cmp_input_and_fmt(reader, fmt, fmt_end);
	}



	template<typename DeviceReader, typename Arg1, typename ...Args>
	DeviceReader scanf_from(DeviceReader reader, char const *fmt, Arg1 &arg, Args &...args)
	{
		auto *fmt_end = fmt + strlen(fmt);
		auto *arg_pos = std::find(fmt, fmt_end, '{');
		if(arg_pos == fmt_end)
			throw runtime_error("too many args");

		reader = internal::cmp_input_and_fmt(reader, fmt, arg_pos);
		reader = read_from(reader, arg, arg_pos);

		auto *arg_end = std::find(arg_pos, fmt_end, '}');
		if(arg_end == fmt_end)
			throw runtime_error("closing brace expected");

		return scanf_from(reader, arg_end + 1, args...);
	}

} // namespace: string

using string::write_to;
using string::printf_to;
using string::print_to;

using string::read_fom;
using string::scanf_from;

} // namespace: light

